﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Documents;
using Lucene.Net.Analysis.Standard;
using JSC.Portal.Module;

namespace JSC.Portal.Search
{
    public class SearchIndexBuilder
    {
		private Directory _indexDirectory;
		private IndexWriter _indexWriter;
		private bool _isClosed = false;
		private bool _rebuildIndex;

		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="physicalIndexDir">Location of the index files.</param>
		/// <param name="rebuildIndex">Flag to indicate if the index should be rebuilt. 
		/// NOTE: you can not update or delete content when rebuilding the index.
		/// </param>
        public SearchIndexBuilder(string physicalIndexDir, bool rebuildIndex)
		{
            _indexDirectory = FSDirectory.GetDirectory(physicalIndexDir, false);
			_rebuildIndex = rebuildIndex;

			InitIndexWriter();
		}

		/// <summary>
		/// Add content to be indexed.
		/// </summary>
		/// <param name="searchContent"></param>
		public void AddContent(SearchableContent searchContent)
		{
			if (_indexWriter == null)
			{
				InitIndexWriter();
			}
			_indexWriter.AddDocument(BuildDocumentFromSearchContent(searchContent));
		}

		/// <summary>
		/// Update existing content in the search index.
		/// </summary>
		/// <param name="searchContent"></param>
		public void UpdateContent(SearchableContent searchContent)
		{
			if (_rebuildIndex)
			{
				throw new InvalidOperationException("Cannot update documents when rebuilding the index.");
			}
			else
			{
				// First delete the old content
				DeleteContent(searchContent);
				// Now add the content again
				AddContent(searchContent);
			}
		}

		/// <summary>
		/// Delete existing content from the search index.
		/// </summary>
		/// <param name="searchContent"></param>
		public void DeleteContent(SearchableContent searchContent)
		{
			if (_rebuildIndex)
			{
				throw new InvalidOperationException("Cannot delete documents when rebuilding the index.");
			}
			else
			{
				_indexWriter.Close();
				_indexWriter = null;

				// The path uniquely identifies a document in the index.
				Term term = new Term("path", searchContent.Path);
				IndexReader rdr = IndexReader.Open(_indexDirectory);
				rdr.DeleteDocuments(term);
				rdr.Close();
			}
		}

        public void UpdateContentFromModule(ModuleBase module)
		{
			ISearchable searchableModule = module as ISearchable;
			if (searchableModule != null)
			{
				IEnumerable<SearchableContent> searchContentList = searchableModule.GetAllSearchableContent();
				foreach (SearchableContent searchContent in searchContentList)
				{
					UpdateContent(searchContent);
				}
			}
		}

		/// <summary>
		/// Close the IndexWriter (saves the index).
		/// </summary>
		public void Close()
		{
			if (! _isClosed && _indexWriter != null)
			{
				_indexWriter.Optimize();
				_indexWriter.Close();
				_isClosed = true; 
			}
		}

		private void InitIndexWriter()
		{
			if (! IndexReader.IndexExists(_indexDirectory) || _rebuildIndex)
			{
				_indexWriter = new IndexWriter(_indexDirectory, new StandardAnalyzer(), true);
			}
			else
			{
				_indexWriter = new IndexWriter(_indexDirectory, new StandardAnalyzer(), false);
			}
		}

		private Document BuildDocumentFromSearchContent(SearchableContent searchContent)
		{
			Document doc = new Document();
            doc.Add(new Field("name", searchContent.Name, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("description", searchContent.Description, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("contents", searchContent.Contents, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("author", searchContent.Author, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("moduletype", searchContent.ModuleType, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("path", searchContent.Path, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("category", searchContent.Category, Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("datecreated", searchContent.DateCreated.ToString("u"), Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("datemodified", searchContent.DateModified.ToString("u"), Field.Store.YES, Field.Index.TOKENIZED));
            doc.Add(new Field("sectionid", searchContent.SectionId.ToString(), Field.Store.YES, Field.Index.NO));

			return doc;
		}

		#region IDisposable Members

		public void Dispose()
		{
			Close();
		}

		#endregion
	}
}
